package com.superbytecode.cloud.safety.ratelimit;

import com.superbytecode.cloud.common.context.SpringContextUtil;
import com.superbytecode.cloud.common.conts.Duration;
import com.superbytecode.cloud.common.utils.http.HttpRequestUtil;
import com.superbytecode.cloud.safety.ratelimit.exception.RateLimitException;
import com.superbytecode.cloud.safety.ratelimit.model.RateLimitInfo;
import com.superbytecode.cloud.safety.ratelimit.model.RateLimitReq;
import com.superbytecode.cloud.safety.ratelimit.model.RateLimitRule;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.DigestUtils;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * All rights Reserved, Designed By www.super-master.com
 *
 * @ProjectName: beacon-cloud
 * @Package: com.superbytecode.cloud.safety.ratelimit.limiter
 * @ClassName: AbstractRateLimit
 * @Description: [限流器模板服务]
 * <p> 模板方法：子类需要实现obtainKey方法 </p>
 * @Author: [Lance Ting]
 * @Date: 2024/11/12 0:13
 * @Version: V1.0
 * @Copyright: 2024 www.super-master.com Inc. All rights reserved.
 * TODO: 注意,本文件Lance Ting所作,如果转载或使用请标明具体出处!
 **/
@Slf4j
public abstract class RateLimiter {

    /**
     * 获取key
     *
     * @param rateLimitReq 请求参数
     * @return key
     */
    protected abstract String obtainKey(RateLimitReq rateLimitReq);

    /**
     * 限流是否允许通过
     *
     * @param rateLimitReq 请求参数
     * @return true-通过，false-未通过
     */
    @SuppressWarnings("unchecked")
    public boolean passed(RateLimitReq rateLimitReq) {
        //
        String cacheKey = obtainCacheKey(rateLimitReq);
        RedissonClient redissonClient = SpringContextUtil.getBean(RedissonClient.class);
        RLock lock = redissonClient.getLock(cacheKey);
        RedisTemplate<String, Object> redisTemplate = redisTemplate();
        try {
            lock.lock(5, TimeUnit.SECONDS);
            RateLimitInfo rateLimitInfo = (RateLimitInfo) redisTemplate.opsForValue().get(cacheKey);
            if (rateLimitInfo == null) {
                rateLimitInfo = new RateLimitInfo().setRuleMap(new HashMap<>());
            }
            // 获取当前时间
            long applyLastTime = System.currentTimeMillis();
            rateLimitInfo.setApplyLastTime(applyLastTime);
            Map<String, RateLimitRule> ruleMap = rateLimitInfo.getRuleMap();
            // 解析限流规则
            List<RateLimitRule> rateLimitRules = parseLimitRules(rateLimitReq.getRules());
            for (RateLimitRule rateLimitRule : rateLimitRules) {
                RateLimitRule limitRule = ruleMap.computeIfAbsent(rateLimitRule.getRule(), l -> rateLimitRule);
                LinkedList<Long> applyTimeList = limitRule.getApplyTimeList();
                Integer limit = limitRule.getLimit();
                // 如果队列还没满，则允许通过，并添加当前时间戳到队列开始位置
                if (applyTimeList.size() < limit) {
                    applyTimeList.addFirst(applyLastTime);
                    continue;
                }
                // 队列已满（达到限制次数），则获取队列中最早添加的时间戳
                Long applyTimeLast = applyTimeList.getLast();
                // 用当前时间戳 减去 最早添加的时间戳
                if (applyLastTime - applyTimeLast <= limitRule.getTimeWindow() * 1000) {
                    log.warn("触发限流规则: {}", limitRule.getRule());
                    //若结果小于等于timeWindow，则说明在timeWindow内，通过的次数大于limit 不允许通过
                    return false;
                } else {
                    // 若结果大于timeWindow，则说明在timeWindow内，通过的次数小于等于limit
                    // 允许通过，并删除最早添加的时间戳，将当前时间添加到队列开始位置
                    applyTimeList.removeLast();
                    applyTimeList.addFirst(applyLastTime);
                }
            }
            // 获取最大时间窗口
            Long maxTimeWindow = rateLimitRules.stream() //
                    .max(Comparator.comparingLong(RateLimitRule::getTimeWindow)) //
                    .orElseThrow(() -> new RateLimitException("rate limit max time window value must be not null")) //
                    .getTimeWindow();
            redisTemplate.opsForValue().set(cacheKey, rateLimitInfo, maxTimeWindow, TimeUnit.SECONDS);
            return true;
        } catch (Exception e) {
            log.error("rate limit pass checked error:", e);
            throw new RateLimitException("服务忙，请稍后重试");
        } finally {
            if (lock != null) {
                lock.unlock();
            }
        }
    }

    // -----------------------------------------------------------------------------------------------------------------

    /**
     * 获取缓存（限流规则）key值
     *
     * @param rateLimitReq 请求参数
     * @return key值
     */
    private String obtainCacheKey(RateLimitReq rateLimitReq) {
        // 获取求情路径
        HttpServletRequest httpRequest = HttpRequestUtil.obtainRequest();
        String requestURI = httpRequest.getMethod() + httpRequest.getRequestURI();
        log.info("rate limit request params，uri:{}, el:{}. rule:{}", requestURI, rateLimitReq.getSpEl(), rateLimitReq.getRules());
        String key = obtainKey(rateLimitReq);
        String md5 = DigestUtils.md5DigestAsHex((requestURI + key).getBytes());
        return RateLimitConts.KEY_RATE_LIMIT_RULE + md5;
    }


    @SuppressWarnings("unchecked")
    private RedisTemplate<String, Object> redisTemplate() {
        return SpringContextUtil.getBean("redisTemplate", RedisTemplate.class);
    }

    /**
     * 解析限流规则
     *
     * @param rules 规则
     * @return 限流规则列表
     */
    private List<RateLimitRule> parseLimitRules(String[] rules) {
        if (ArrayUtils.isEmpty(rules)) {
            throw new RateLimitException("rate limit rules can't be empty!");
        }
        return Arrays.stream(rules).map(rule -> {
            // rule 数据格式：2/3s-->每3秒内限制2次请求
            String[] ruleArgs = rule.split("/");
            if (ruleArgs.length != 2) {
                throw new RateLimitException("rate limit rule args error:" + rule);
            }
            RateLimitRule rateLimitRule = new RateLimitRule();
            try {
                // 请求次数
                int limitArg = Integer.parseInt(ruleArgs[0].trim());
                rateLimitRule.setLimit(limitArg);
                // 时间周期
                String periodArg = ruleArgs[1].trim();
                long timeArg = Long.parseLong(periodArg.substring(0, periodArg.length() - 1));
                String timeUnitArg = periodArg.substring(periodArg.length() - 1);
                Long limitTimeOfSecond = Duration.ofSecond(timeArg, timeUnitArg);
                rateLimitRule.setTimeWindow(limitTimeOfSecond);
                rateLimitRule.setApplyTimeList(new LinkedList<>());
            } catch (Exception exception) {
                throw new RateLimitException("rate limit rule args error:" + rule);
            }
            return rateLimitRule;
        }).collect(Collectors.toList());
    }
}